﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace TermitesMagnets
{
    class MyNode
    {
        public int d = Int32.MaxValue;
        public int x;
        public int y;
        public Vector2 pos;
        public MyNode pi = null;
        public bool disabled = false;
    };

    class AIPlayer
    {
        private List<Termite> termites;
        private List<Termite> myArmy;
        private List<TargetRect> targets;
        private List<Magnet> magnets;
        private Magnet self;
        private Command cmd;
        private TargetRect myTarget;
        private const int delta = 3;
        private Level level;
        private Boolean inInterrupt = false;
        private int speedOfMovement;

      
        private MyNode findWayBfs(int divider, Vector2 src, Vector2 dst)
        {

            Board board = GameplayScreen.GlobalGame.CurrentLevel.Board;
            int boxHeight = board.Rows / divider;
            int boxWidth = board.Cols / divider;
            MyNode[,] nodes = new MyNode[divider + 1, divider + 1];
            for(int x=0;x<=divider;x++)
                for(int y=0;y<=divider;y++)
                {
                    nodes[x, y] = new MyNode();
                    MyNode current = nodes[x,y];
                    current.x = x;
                    current.y = y;
                    current.pos = new Vector2((float)((x+0.5) * boxWidth),
                                              (float)((y+0.5) * boxHeight));
                    foreach (Obstacle obst in board.Obstacles)
	                {
		                if (obst.Rect.Intersects(new Rectangle(x*boxWidth,
                                                               y*boxHeight,
                                                               boxWidth,
                                                               boxHeight)))
                            current.disabled = true;
	                }
                }
            Queue<MyNode> Q = new Queue<MyNode>();
            int srcX = (int)(src.X / boxWidth);
            int srcY = (int)(src.Y / boxHeight);
            int dstX = (int)(dst.X / boxWidth);
            int dstY = (int)(dst.Y / boxHeight);
            Q.Enqueue(nodes[srcX, srcY]);
            nodes[srcX, srcY].d = 0;
            while (Q.Count>0)
            {
                MyNode current = Q.Dequeue();
                // get edges
                List<MyNode> neighbors = new List<MyNode>();
                int upY = current.y-1;
                int downY = current.y+1;
                int leftX = current.x -1;
                int rightX = current.x + 1;
                if(upY>0)
                {
                    if(!nodes[current.x, upY].disabled)
                        neighbors.Add(nodes[current.x, upY]);
                }
                if(leftX>0)
                {
                    if(!nodes[leftX, current.y].disabled)
                        neighbors.Add(nodes[leftX, current.y]);
                }
                if(downY<divider)
                {
                    if(!nodes[current.x, downY].disabled)
                        neighbors.Add(nodes[current.x, downY]);
                }
                if(rightX<divider)
                {
                    if(!nodes[rightX, current.y].disabled)
                        neighbors.Add(nodes[rightX, current.y]);
                }
                foreach (MyNode n in neighbors)
                {
                    if(n.d > current.d + 1)
                    {
                        n.d = current.d + 1;
                        n.pi = current;
                        if((n.x == dstX) && (n.y == dstY))
                        {
                            return n;
                        }
                        Q.Enqueue(n);
                    }
                }
            }
            // haven't found
            return null;

        }

        public AIPlayer(int difficulty)
        {
            cmd = null;
            myTarget = null;
            myArmy = new List<Termite>();
            level = GameplayScreen.GlobalGame.CurrentLevel;
            if (difficulty == 0)
            {
                speedOfMovement = 60;
            }
            else if (difficulty == 1)
            {
                speedOfMovement = 120;
            }
            else if (difficulty == 2)
            {
                speedOfMovement = 170;
            }
            else if (difficulty == 3)
            {
                speedOfMovement = 210;
            }
            else
            {
                throw new NotImplementedException("add more difficulties");
            }
        }

        public void setParameters(Magnet self, ref List<Termite> termites, ref List<TargetRect> targets, 
            ref List<Magnet> magnets)
        {
            this.self = self;
            this.termites = termites;
            this.targets = targets;
            this.magnets = magnets;

            foreach (TargetRect t in this.targets)
            {
                if (t.ColorIndex == self.ColorIndex)
                {
                    myTarget = t;
                    break;
                }
            }

            for (int i = 0; i < termites.Count; i++)
            {
                if (termites[i].ColorIndex == self.ColorIndex)
                {
                    myArmy.Add(termites[i]);
                }
            }
        }

        public bool isDoingCommand()
        {
            return cmd != null;
        }

        private Pwu getEnabledPwu()
        {
            // Interrupt
            Pwu foundPwu = null;
            foreach (Pwu pwu in level.Pwus)
            {
                if (pwu.isEnabled)
                {
                    foundPwu = pwu;
                    break;
                }
            }
            return foundPwu;
        }

        private void addGetPwuCommand(Pwu pwu)
        {

            #region handleResume
            Command lastCommand = this.cmd;
            while (lastCommand != null && lastCommand.CmdType == Command.CommandType.Sleep)
            {
                lastCommand = lastCommand.NextCommand;
            }
            Boolean wasEnabled = self.IsEnabled;
            Vector2 oldPosition = self.Position;
            Command restoreEnabledStatusCommand;
            if (wasEnabled)
                restoreEnabledStatusCommand = new Command(Command.CommandType.MagnetOn);
            else
                restoreEnabledStatusCommand = new Command(Command.CommandType.MagnetOff);

            Command firstBreak = new Command(Command.CommandType.Stop);
            Command restorePositionCommand = new Command(Command.CommandType.GoTo);
            restorePositionCommand.MaxSpeed = speedOfMovement;
            restorePositionCommand.Position = oldPosition;

            Command turnOffCommand = new Command(Command.CommandType.MagnetOff);
            firstBreak.NextCommand = turnOffCommand;
            turnOffCommand.NextCommand = restorePositionCommand;
            restorePositionCommand.NextCommand = restoreEnabledStatusCommand;
            restoreEnabledStatusCommand.NextCommand = lastCommand;
            #endregion

            inInterrupt = true;
            //Pick up termites
            Vector2 pos = pwu.Position;
            Command c1 = new Command(Command.CommandType.MagnetOff);
            Command c2 = new Command(Command.CommandType.GoTo);
            Command c3 = new Command(Command.CommandType.EndInterrupt);
            c2.Position = pos;
            c1.NextCommand = c2;
            c2.MaxSpeed = speedOfMovement;
            c2.NextCommand = c3;
            // Add resume command sequence
            c3.NextCommand = firstBreak;
            this.cmd = c1;
        }

        private void addInterruptToPlayerCommand(TargetRect target)
        {
            Command c1 = new Command(Command.CommandType.MagnetOff);
            Command c2 = new Command(Command.CommandType.GoTo);
            c2.MaxSpeed = speedOfMovement;
            c2.Position = target.DamagingPoint;
            Command c3 = new Command(Command.CommandType.MagnetOn);
            Command c4 = new Command(Command.CommandType.Sleep);
            c4.Sleep = 1.5;
            c3.NextCommand = c4;
            c2.NextCommand = c3;
            c1.NextCommand = c2;
            this.cmd = c1;
        }

        private TargetRect findBetterTarget()
        {
            //Interupt to other players
            double bestTime = Double.MaxValue;
            TargetRect bestTarget = null;
            foreach (TargetRect t in this.targets)
            {
                if (t == myTarget) continue;
                float tTime = estimateTimeToFinish(t);
                if (tTime < bestTime)
                {
                    bestTime = tTime;
                    bestTarget = t;
                }
            }
            float myTime = estimateTimeToFinish(myTarget);
            if (bestTime < myTime)
                return bestTarget;
            else
                return null;
        }

        private void addGetTermitesCommand()
        {
            //Pick up termites
            Vector2[] hotspot = findTermiteHotSpot();
            Command c1 = new Command(Command.CommandType.MagnetOff);
            Command c2 = new Command(Command.CommandType.GoTo);
            c2.MaxSpeed = speedOfMovement;

            Vector2 center = new Vector2(myTarget.Position.X + (TargetRect.Width / 2),
            myTarget.Position.Y + (TargetRect.Height / 2));
            Vector2 fix = myTarget.DamagingPoint - center;
            Vector2 targetPos = center + fix/2;
            // Solve the maze
            MyNode current = null;

            for (int i = 0; i < hotspot.Length; i++)
            {
                for (int j = 5; j < 30; j++)
                {
                    current = findWayBfs(j, targetPos, hotspot[i]);
                    if (current != null)
                    {
                        break;
                    }
                }
                if (current != null)
                {
                    c2.Position = hotspot[i];
                    break;
                }
            }
            
            if (current == null)
            {
                return;
            }
            Command listHead = new Command(Command.CommandType.Stop);
            Command cCommand = listHead;
            while (current.pi != null)
            {
                current = current.pi;
                cCommand.NextCommand = new Command(Command.CommandType.GoTo);
                cCommand = cCommand.NextCommand;
                cCommand.Position = current.pos;
                cCommand.MaxSpeed = 40;
                cCommand.Sleep = 0.4;
            }

            Command c3 = new Command(Command.CommandType.MagnetOn);
            // Placeholder
            Command c4 = new Command(Command.CommandType.Sleep);
            c4.Sleep = 1;
            c3.NextCommand = listHead;
            cCommand.NextCommand = c4;
            c2.NextCommand = c3;
            c1.NextCommand = c2;
            this.cmd = c1;
        }

        private Boolean checkTermiteLevel()
        {
            float precentage = 100 * (float)myTarget.TermiteCount / myTarget.TargetTermiteCount;
            return precentage > 30;
        }

        private void refreshCommands()
        {
            if (!inInterrupt)
            {
                Pwu foundPwu = getEnabledPwu();
                // If there is pwu enabled, go fetch it!
                if (foundPwu != null)
                    addGetPwuCommand(foundPwu);
            }

            if (!isDoingCommand())
            {
                double myScore = myTarget.Eatten;
                TargetRect bestTarget = findBetterTarget();
                if ((Game1.Rand.NextDouble() < 0.5) &&
                    (bestTarget != null))
                {
                    addInterruptToPlayerCommand(bestTarget);
                }
                else
                {
                    addGetTermitesCommand();
                }
            }
        }

        private void handleCommands(GameTime gameTime)
        {
            if (cmd != null)
            {
                switch (cmd.CmdType)
                {
                    case Command.CommandType.Stop:
                        self.Speed = Vector2.Zero;
                        this.cmd = this.cmd.NextCommand;
                        break;
                    case Command.CommandType.GoTo:
                        if (isDestinationReached())
                        {
                            //System.Diagnostics.Debug.WriteLine("is reached " + self.ColorIndex+" deltaX="+
                            //(self.Position.X - this.cmd.Position.X) + " deltaY=" + (self.Position.Y - this.cmd.Position.Y));
                            self.Speed = Vector2.Zero;
                            cmd = cmd.NextCommand;
                        }
                        else //accelerate to target
                        {
                            if (self.Speed.Length() < cmd.MaxSpeed)
                            {
                                System.Diagnostics.Debug.WriteLine("speed length " + self.Speed.Length());
                                Vector2 diff = this.cmd.Position - self.Position;
                                Vector2 speedDiff = diff / diff.Length();
                                self.Speed += speedDiff * 5;
                                //System.Diagnostics.Debug.WriteLine("isn't reached " + self.ColorIndex);
                            }
                        }
                        break;
                    case Command.CommandType.MagnetOff:
                        self.IsEnabled = false;
                        cmd = cmd.NextCommand;
                        break;

                    case Command.CommandType.MagnetOn:
                        self.IsEnabled = true;
                        cmd = cmd.NextCommand;
                        break;

                    case Command.CommandType.Sleep:
                        if (cmd.Sleep >= 0)
                        {
                            cmd.Sleep -= gameTime.ElapsedGameTime.TotalSeconds;
                        }
                        else
                        {
                            cmd = cmd.NextCommand;
                        }
                        break;
                    case Command.CommandType.EndInterrupt:
                        inInterrupt = false;
                        cmd = cmd.NextCommand;
                        break;

                    default:
                        break;
                }
            }
        }

        public void doCommand(GameTime gameTime)
        {
            refreshCommands();
            handleCommands(gameTime);
        }

        private Vector2[] findTermiteHotSpot()
        {
            Board board = GameplayScreen.GlobalGame.CurrentLevel.Board;
            int divider = 5;
            int boxHeight = board.Rows / divider;
            int boxWidth = board.Cols / divider;
            Vector2[] ans = new Vector2[(divider + 1) * (divider + 1)];
            int[,] counter = new int[divider+1, divider+1];
            int maxCounter = 0;
            float maxX = 0;
            float maxY = 0;

            int myX = (int)myTarget.Position.X / boxWidth;
            int myY = (int)myTarget.Position.Y / boxHeight;
            // Avoid collectiong termites from your target
            for (int x = myX - 1; x < myX + 1; x++)
            {
                for (int y = myY - 1; y < myY + 1; y++)
                {
                    if( (x >= 0) && (x < divider) && (y >= 0) && (y < divider))
                        counter[x, y] = int.MinValue;   
                }
            }
            foreach (Termite termite in myArmy)
            {
                int curX = (int)termite.Position.X / boxWidth;
                int curY = (int)termite.Position.Y / boxHeight;
                counter[curX, curY]++;
                if(maxCounter<counter[curX, curY])
                {
                    maxCounter = counter[curX, curY];
                    maxX = curX;
                    maxY = curY;
                }
            }

            PointWithCounter[] tmp = new PointWithCounter[(divider + 1) * (divider + 1)];

            for (int i = 0; i < divider + 1; i++)
            {
                for (int j = 0; j < divider + 1; j++)
                {
                    tmp[i * (divider + 1) + j] = new PointWithCounter(i, j, counter[i, j]);
                }
            }

            Array.Sort(tmp, delegate(PointWithCounter p1, PointWithCounter p2)
            {
                return p2.ctr - p1.ctr;
            });

            for (int i = 0; i < (divider + 1) * 2; i++)
            {
                ans[i] = new Vector2((0.5f + tmp[i].x) * boxWidth, (0.5f + tmp[i].y) * boxHeight);
            }

            // return the center of the hot box
            return  ans;
        }

        private float estimateTimeToFinish(TargetRect t)
        {
            double left = 100 - t.Eatten;
            float speed = t.EatingSpeed();
            float timeLeft = (float)left / speed;
            return timeLeft;
        }

        private bool isDestinationReached()
        {
            return (Math.Abs(self.Position.X - this.cmd.Position.X) < delta &&
                    Math.Abs(self.Position.Y - this.cmd.Position.Y) < delta);
        }

        private bool isAtHome()
        {
            return (self.Position.X >= myTarget.Position.X &&
                self.Position.X <= (myTarget.Position.X + TargetRect.Width) &&
                self.Position.Y >= myTarget.Position.Y &&
                self.Position.Y <= (myTarget.Position.Y + TargetRect.Height));
        }

        class PointWithCounter
        {
            public int x;
            public int y;
            public int ctr;

            public PointWithCounter(int x, int y, int ctr)
            {
                this.x = x;
                this.y = y;
                this.ctr = ctr;
            }

            public override string ToString()
            {
                return "X: " + x + " Y: " + y + " Counter: " + ctr;
            }
        }
    }
}
